/*
 *
 * graphic.c
 *
 * Nucleo grafico do SO.
 * Controle do dispositivo de video.
 *
 * Copyright 2010 Wendell Silva Soares <wendell@xPhoenix>
 *
 * ===== Escrevendo na tela =====
 *
 * 	Codigo para escrever para a tela. Para escrever na tela, eh necessario
 * controlar o rolamento da tela quando necessário. Pode ser legal ter
 * diferentes cores na tela também.
 * 	Felizmente, com o VGA da para fazer isso simplesmente:
 * Ele nos dá um pedaço de memória que pode ser escrito tanto o a informacao de
 * atributo (cores) e a informacao do caractere.
 * 	O controlador VGA cuidará automaticamente de desenhar as atualizações feitas
 * na tela. Rolar a tela é administrado pelo nosso software do kernel. Este é
 * tecnicamente o primeiro driver.
 *
 * 	Como mencionado, acima, a memória de texto é simplesmente um pedaço da
 * memória no nosso espaço de endereçamento.
 * 	Esse buffer está localizado em 0xB8000, na memória física. O buffer é do
 * tipo 'short', significando que cada item nesta memória de texto está num
 * arranjo (array) de 16-bits, em vez do usual 8-bits.
 * 	Cada elemento de 16-bit no buffer da memória de texto pode ser quebrado em
 * 8-bits 'superiores' e em 8-bits 'inferiores'.
 * 	Os 8-bits inferiores de cada elementos indica o para o controle de vídeo
 * qual caractere vai aparecer na tela. Os 8 bits superiores são usados para
 * definir as cores do  primeiro plano e do fundo que vão ser usadas no
 * caractere desenhado.
 *
 * |15	         12 | 11	            8 | 7	                         0|
 * |  Fundo	        |  Primeiro plano     |        Caractere              |
 *
 * 		Os 8-bits superiores para cada elemento de 16-bit do texto é chamado de
 * 'byte de atributo', e os 8-bits inferiores são chamados 'byte de caractere'.
 * 		Como você pode ver na tabela anterior, foi mapeado cada uma das partes
 * do elemento de texto de 16-bit, o byte de atributo é quebrado em 2 partes
 * diferentes de 4-bit: 1 representa a cor de fundo e 1 representa a cor do
 * primeiro plano. Agora, por causa do fato de somente 4-bit definir cada cor,
 * então pode-se somente ter no máximo 16 cores diferentes para escolher
 * (Usando a equação (num bits ^ 2) - 4^2 = 16).
 * 	Abaixo está a tabela para a palheta padrão de 16-cores.
 *
 * Valor	Cor	     Valor	  Cor
 * 0	 PRETO	     8	    CINZA ESCURO
 * 1	 AZUL	     9	    AZUL LEVE
 * 2	 VERDE	     10	    VERDE LEVE
 * 3	 CIANO	     11	    CIANO LEVE
 * 4	 VERMELHO    12	    VERMELHO LEVE
 * 5	 MAGENTA	 13	    MAGENTA LEVE
 * 6	 MARRON	     14	    MARRON LEVE
 * 7	 CINZA LEVE  15	    BRANCO
 *
 * 		Finalmente, para acessar um índice particular da memória, há uma equação
 * que deveremos usar. A memória do modo texto é uma simples área 'linear'
 * (ou flat) da memória, mas o controlador de vídeo faz aparecer como se fosse
 * uma matriz de 80x25 de valores de 16-bit. Cada linha do texto é uma seqüência
 * de memória; eles seguem outro.
 * 		Nós portanto tentaremos quebrar a tela em linhas horizontais. O melhor
 * meio é usar a seguinte equação:
 * 			índice = (valor_y * largura_da_tela) + valor_x;
 *
 * 		Esta equação mostra como acessar o índice do arranjo da memória de texto,
 * para dizer (3, 4), nós poderemos usar esta equação para encontra-la
 * 4 * 80 + 3 é 323.
 * 		Por este meio para desenhar na  localização (3, 4) da tela, nós precisa-
 * remos escrever algo similar a isto:
 *
 * unsigned short *where = (unsigned short *)0xB8000 + 323;
 * *where = character | (attribute << 8);
 *
 * 		O método de rolamento é bem interessante : Nós pegamos um pedaço da memória
 * de texto começando pela linha 1 (NÃO é linha 0),  e copiamos para o topo da
 * linha 0. Isto basicamente move para cima uma linha .
 * 		Para completar o rolamento, nós apagamos a última linha do texto
 * escrevendo espaços com seus bytes de atributos. A função putchar é possivelmente
 * a função mais complicada deste arquivo.
 * 		É também a maior, porque necessitamos tratar qualquer linha nova
 * (newlines) ('\n'), retornos de carruagem ('\r'), e backspaces ('\b').
 * 		Tambem incuido a função para as cores da tela (settextcolor)
 *
 */

#include <keyboard.h>
#include <graphic.h>

// Endereco base da memoria de video
unsigned short *videoram;

int color = 0x0F;
int copyColor;

// Posicao relativa do cursor no prompt
int pos_x = 0;
int pos_y = 0;

// Posicao absoluta do cursor no prompt
// Índice = [(y * largura) + x]
inline int posCur() {
	return pos_y*COL + pos_x;
}

// Scrool: sobe a tela, a medida que o texto chega na ultima linha
void scroll(void) {
	// Se jah passei da ultima linha...
	if(pos_y >= LIN) {
		int i, j;
		//"empurro" as linhas pra cima:
		// linha0=linha1, linha1=linha2, ... linha n-1 = linha n
		for(i=1; i<=LIN; i++) {
			for(j=0; j<COL; j++) {
				videoram[(i-1)*COL + j] = videoram[i*COL + j];
			}
		}
		//reposiciono os cursores.
		pos_x=0;
		pos_y = LIN-1;
	}
}

// Posiciona o cursor na tela
inline void updateCursor(void) {

	unsigned temp = posCur();

	/* Este manda um comando para os índices 14 e 15 no
	 *  Registrador de Controle CRT do controle VGA. Esses
	 *  são os bytes superior e inferior do índice que mostra
	 *  onde o cursor do hardware está 'piscando'.
	 *  Para aprender mais, você deveria procurar alguns documentos específicos
	 *  de programação para VGA.
	 *  Um grande começo para gráficos:
	 *  http://www.brackeen.com/home/vga */
	outportb(0x3D4, 14);
	outportb(0x3D5, temp >> 8);
	outportb(0x3D4, 15);
	outportb(0x3D5, temp);
}


// Limpa a tela
inline void cls() {
	int i, j;

	pos_x = 0;
	pos_y = 0;

	for(i=1; i<=LIN; i++) {
		for(j=0; j<COL; j++) {
			putchar(SPACE);
		}
	}

	pos_x = 0;
	pos_y = 0;

	updateCursor();
}


//Imprime um caractere na tela
inline void putchar(uchar c) {

	ushort *where;

	/**
	 * Tipo short (16 bits)
	 * Exemplo: color = 0x0F
	 * color=0x0F => 00000000.00001111 (15)
	 * color << 8 => 00001111.00000000 (3840)
	 */
	unsigned att = color << 8;


	//backspace
	if(c == 0x08) {
		if(pos_x != 0) {
			--pos_x;
			putchar(' ');
			--pos_x;
		}
	}
	else if(c == '\t') { //tab
		pos_x = (pos_x + 8) & ~(8 - 1);
	}
	else if(c == '\r') {
		pos_x = 0;
	}
	else if(c == '\n') {
		pos_x = 0;
		++pos_y;
	}
	//Se c for imprimivel
	else if(c >= ' ' || c == '\a') {
		//posicao na tela.
		where = videoram + posCur();

		/**
		 *  c: letra 'A' (65)  => 01000001
		 *  att     =>   00001111.00000000 (se color = 0x0F)
		 *  *where  =>   00001111.01000001
		 *                  COR  | CARACTER
		 */
		//conteudo na posicao where.
		*where = c | att;	// Caractere E atributos: cor
		++pos_x;
	}

	//Se o cursor estah no final da linha, vah pra proxima linha
	if(pos_x >= COL) {
		pos_x = 0;
		pos_y++;
	}

	//Move a tela e atualiza o cursor
	scroll();
	updateCursor();
}

// Imprime uma string na tela
void print(char s[]) {
	int i;
	for(i=0; s[i]!='\0'; ++i)
		putchar(s[i]);
}

void println(char s[]) {
	print(s);
	putchar('\n');
}

// Seta as cores que serao usadas
inline void setTextColor(uchar caracter, uchar fundo) {
	//Os 4 primeiros bits sao a cor do fundo da tela
	//Os 4 ultimos bits sao a cor do caractere

	//guarda a cor atual para restaurar depois, se necessario
	copyColor = color;

	/**
	 * caractere BRANCO (15) => 00001111
	 * 		     0x0F        => 00001111
	 *
	 * fundo CINZA ESC  (8)  => 00001000
	 *
	 * a) BRANCO & 0x0F      => 00001111
	 * b) Fundo << 4         => 10000000
	 * color = a | b         => 10001111
	 *                         fundo caractere
	 */
	color = (fundo << 4) | (caracter & 0x0F);
}

void restoreColor() {
	color = copyColor;
}

// Seta o cursor para a posicao desejada
inline void gotoXY(int x, int y) {
	if(x+1 && y+1) {
		pos_x = x;
		pos_y = y;
		updateCursor();
	}
}

//Retorna a posicao atual do cursor em X e em Y
inline int getX() {
	return pos_x;
}

inline int getY() {
	return pos_y;
}

// Inicia o servico de video
inline void initializeVideo(void) {
	videoram = VGA_BASE;
	cls();
}
